#!/bin/bash
#author: xieziqiang
#email: xie.zi.qiang@outlook.com

# https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html
# https://www.elastic.co/cn/downloads/elasticsearch
# https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.14.3-linux-x86_64.tar.gz
# https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.22-linux-x86_64.tar.gz
set -e
#****** vars *********#
ES_VERSION="8.14.3"
ES_INSTALL_PATH="/usr/local"
ES_DATA_PATH="/data/es-data"
ES_LOGS_PATH="/data/es-logs"
ES_SECURITY="true"     # true or false
ES_HTTP_PORT="9200"
ES_JVM_XMS="512m"
ES_JVM_XMX="512m"
ES_USER="elasticsearch"
ES_WAIT_TIME="100" # second ,10 times. example: 100,200,300

#**** certificate vars *******#
ES_CERTS_PATH="${ES_INSTALL_PATH}/elasticsearch/config/certs"
ES_SEFT_CA="elastic-stack-ca.p12"
ES_HTTP_CERT="elastic-http.p12"
ES_TRANSPORT_CERT="elastic-certificates.p12"
ES_CERT_DAYS="3560"  # valid days
ES_CERT_IPS="$(hostname -I | awk '{print $1}')" # cluster example IPs: 10.0.0.20,10.0.21,10.0.22
ES_CERT_DNS="localhost" # example dns: es1.xieziqiang.org,es2.xieziqiang.org 

#****** functions *********#
color() {
    local message="$1"
    local status="$2"
    local terminal_width
    terminal_width=$(tput cols)
    local middle_column=$(( terminal_width / 2))
    local SETCOLOR_SUCCESS="\033[1;32m"  # 绿色
    local SETCOLOR_FAILURE="\033[1;31m"  # 红色
    local SETCOLOR_WARNING="\033[1;33m"  # 黄色
    local SETCOLOR_NORMAL="\033[0m"      # 重置颜色
    printf '%s\n\033[%sG' "${message}"  "${middle_column}"
    # Print the status message
    printf "["
    case ${status} in
        success|0)
            echo -en  "${SETCOLOR_SUCCESS}  OK  "
            ;;
        failure|1)
            echo -en  "${SETCOLOR_FAILURE}FAILED"
            ;;
        *)
            echo -en "${SETCOLOR_WARNING}WARNING"
            ;;
    esac
    echo -e "${SETCOLOR_NORMAL}]"
}

judge_current_user () {
    if [ "$(id -u)" = "0" ];then
        color "current user is administrator" 0
    else
        color "current user is not administrator" 1
        exit
    fi
}

create_dir () {
    local i
    for i in "$@";do
        if [ ! -d "$i" ];then
            mkdir -pv "$i" && color "$i dir is created" 0
        else
            color "$i dir is existed." 2
        fi
    done
}

create_user () {
    if ! id "$1" &> /dev/null;then
        useradd -r -M -s /sbin/nologin "$1" \
        && color "$1 user is created." 0 \
        || { color "$1 user create failed.";exit; }
    else
        color "$1 user is existed." 2
    fi
}

port_avilable () {
    for((i=1;i<=10;i++));do
    sleep $((ES_WAIT_TIME/10))
    set +e
    if ss -ntl | grep ${ES_HTTP_PORT} &>/dev/null;then
       color "start elasticsearch.service is success." 0
       break
    else
        echo "$i/10  detect elasticsearch service is no response..."
        if [ $i -eq 10 ];then
            color "try time is 10/10,elasticsearch.service may unreachable" 1
            exit
        fi
        continue
    fi  
    set -e
done
}

generated_es_cert () {    # arg1：cert file path, arg2: ca file path
    if [ ! -e "$1" ];then
        ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch-certutil cert \
        --ip "${ES_CERT_IPS}" \
        --dns "${ES_CERT_DNS}" \
        --days "${ES_CERT_DAYS}" \
        --pass "" \
        --out "$1" \
        --ca-pass "" \
        --ca "$2"
    fi
}

set_es_certs () {
    create_dir ${ES_CERTS_PATH}

    if [ ! -e ${ES_CERTS_PATH}/${ES_SEFT_CA} ];then
        ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch-certutil ca \
        -out ${ES_CERTS_PATH}/${ES_SEFT_CA} \
        -pass ""
        color "${ES_CERTS_PATH}/${ES_SEFT_CA} generated success" 0
    else
        color "${ES_CERTS_PATH}/${ES_SEFT_CA} is existed,don't generated again." 2
    fi

    if [ ! -e ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT} ];then
        generated_es_cert ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT} \
        ${ES_CERTS_PATH}/${ES_SEFT_CA}
        color "${ES_CERTS_PATH}/${ES_TRANSPORT_CERT} generated success" 0
    else
        color "${ES_CERTS_PATH}/${ES_TRANSPORT_CERT} is existed,don't generated again." 2
    fi

    if [ ! -e ${ES_CERTS_PATH}/${ES_HTTP_CERT} ];then
        generated_es_cert ${ES_CERTS_PATH}/${ES_HTTP_CERT} \
        ${ES_CERTS_PATH}/${ES_SEFT_CA}
        color "${ES_CERTS_PATH}/${ES_HTTP_CERT} generated success" 0
    else
        color "${ES_CERTS_PATH}/${ES_HTTP_CERT} is existed,don't generated again." 2
    fi

    chown  ${ES_USER}:${ES_USER}  ${ES_CERTS_PATH}/${ES_HTTP_CERT}
    chown  ${ES_USER}:${ES_USER}  ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT}
    echo "#####*********************************#####"
    color "all certificate is finshed." 0
    echo "#####*********************************#####"
}

init_es_service () {
    cat > /lib/systemd/system/elasticsearch.service<<EOF
[Unit]
Description=Elasticsearch
Documentation=https://www.elastic.co
Wants=network-online.target
After=network-online.target

[Service]
Type=forking # 因为二进制es的包缺少systemd相关模块，所以只能使用forking类型，包安装的话，这里类型是notify
# the elasticsearch process currently sends the notifications back to systemd
# and for some reason exec does not work (even though it is a child). We should change
# this notify access back to main (the default), see https://github.com/elastic/elasticsearch/issues/86475
NotifyAccess=all
RuntimeDirectory=elasticsearch
PrivateTmp=true
Environment=ES_HOME=${ES_INSTALL_PATH}/elasticsearch
Environment=ES_PATH_CONF=${ES_INSTALL_PATH}/elasticsearch/config
Environment=PID_DIR=${ES_INSTALL_PATH}/elasticsearch
# Environment=ES_SD_NOTIFY=true
# EnvironmentFile=-/etc/default/elasticsearch
WorkingDirectory=${ES_INSTALL_PATH}/elasticsearch
User=${ES_USER}
Group=${ES_USER}
ExecStart=${ES_INSTALL_PATH}/elasticsearch/bin/systemd-entrypoint -p \${PID_DIR}/elasticsearch.pid --quiet
# StandardOutput is configured to redirect to journalctl since
# some error messages may be logged in standard output before
# elasticsearch logging system is initialized. Elasticsearch
# stores its logs in /var/log/elasticsearch and does not use
# journalctl by default. If you also want to enable journalctl
# logging, you can simply remove the "quiet" option from ExecStart.
StandardOutput=journal
StandardError=inherit
# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65535
# Specifies the maximum number of processes
LimitNPROC=4096
# Specifies the maximum size of virtual memory
LimitAS=infinity
# Specifies the maximum file size
LimitFSIZE=infinity
# Disable timeout logic and wait until process is stopped
TimeoutStopSec=0
# SIGTERM signal is used to stop the Java process
KillSignal=SIGTERM
# Send the signal only to the JVM rather than its control group
KillMode=process
# Java process is never killed
SendSIGKILL=no
# When a JVM receives a SIGTERM signal it exits with code 143
SuccessExitStatus=143
# Allow a slow startup before the systemd notifier module kicks in to extend the timeout
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target
EOF
    systemctl daemon-reload
    systemctl enable elasticsearch.service
}

set_es_configfile () {
    cp ${ES_INSTALL_PATH}/elasticsearch/config/{elasticsearch.yml,elasticsearch.yml.bak}
    cat > ${ES_INSTALL_PATH}/elasticsearch/config/elasticsearch.yml<<EOF
#####*********common config**********#############################
path.data: ${ES_DATA_PATH}
path.logs: ${ES_LOGS_PATH}
# network.host: 0.0.0.0
http.host: 0.0.0.0
http.port: ${ES_HTTP_PORT}
#####*********cluster config**********#############################
# transport.host: 0.0.0.0
# cluster.name: xie-es-cluster
# node.name: es-node-002
# discovery.seed_hosts: ["10.0.0.24","10.0.0.27","10.0.0.28"]
# cluster.initial_master_nodes: ["10.0.0.24","10.0.0.27","10.0.0.28"]
# gateway.expected_nodes: 3                  # 7.x
# gateway.expected_data_nodes: 3             # 8.x
# gateway.recover_after_nodes: 2             # 7.x
# gateway.recover_after_data_nodes: 2        # 8.x
# gateway.recover_after_time: 1m

#####*********security config**********#############################
xpack.security.enabled: ${ES_SECURITY}

xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: ${ES_CERTS_PATH}/${ES_HTTP_CERT}

xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate 
xpack.security.transport.ssl.client_authentication: required
# xpack.security.transport.ssl.key: /etc/elasticsearch/certs/instance.key
# xpack.security.transport.ssl.certificate: /etc/elasticsearch/certs/instance.crt
xpack.security.transport.ssl.keystore.path: ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT}
xpack.security.transport.ssl.truststore.path: ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT}
EOF
}

es7.x_start_service () {
    systemctl restart elasticsearch.service
### 当security开启的时候，循环判断服务是否启动，并且生成初始化密码
    if [ "${ES_SECURITY}" = "true" ];then
        HTTP_PREFIX="https"
        for((i=1;i<=10;i++));do
            sleep $((ES_WAIT_TIME/10))
            set +e
            ES_INITIAL_PASSWORD="$(echo 'y' | ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch-setup-passwords auto 2> /dev/null | grep 'PASSWORD')"
            set -e
            if [ -n "${ES_INITIAL_PASSWORD}" ];then
                color "start elasticsearch.service is success." 0
                break
            else
                echo "$i/10  detect elasticsearch service is no response..."
                if [ $i -eq 10 ];then
                    color "try time is 10/10,elasticsearch.service may unreachable" 1
                    exit
                fi
                continue
            fi
        done
    ### 当security关闭的时候，通过端口循环判断服务是否启动
    elif [ "${ES_SECURITY}" = "false" ];then
        HTTP_PREFIX="http"
        port_avilable
    else
        color "fatal error! ES_SECURITY value is wrong." 1
        exit
    fi
}

es8.x_start_service () {
    systemctl restart elasticsearch.service 
    for((i=1;i<=10;i++));do
        sleep $((ES_WAIT_TIME/10))
        set +e
        ES_INITIAL_PASSWORD="$(echo 'y' | ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch-reset-password -u elastic 2> /dev/null | awk '/New value/{print $NF}')"
        set -e
        if [ -n "${ES_INITIAL_PASSWORD}" ];then
            color "start elasticsearch is success." 0
            break
        else
            echo "$i/10  detect elasticsearch service is no response..."
            if [ $i -eq 10 ];then
                color "try time is 10/10,elasticsearch.service may unreachable" 1
                exit
            fi
            continue
        fi
    done

    if [ "${ES_SECURITY}" = "true" ];then
        HTTP_PREFIX="https"
        color "current node security enable" 0
    elif [ "${ES_SECURITY}" = "false" ];then
        HTTP_PREFIX="http"
        color "setting security false ..." 2
        sed -i "/xpack.security.enabled: true/c xpack.security.enabled: false" ${ES_INSTALL_PATH}/elasticsearch/config/elasticsearch.yml
        systemctl restart elasticsearch.service 
        port_avilable
    fi
}
#####********** main functions*************#####
process_file () {
    if [ ! -e "elasticsearch-${ES_VERSION}-linux-x86_64.tar.gz" ];then
        color "current directory dont exist elasticsearch-${ES_VERSION}-linux-x86_64.tar.gz" 1
        exit
    fi

    tar -xvf "elasticsearch-${ES_VERSION}-linux-x86_64.tar.gz" -C "${ES_INSTALL_PATH}"
    ln -nfs ${ES_INSTALL_PATH}/elasticsearch-${ES_VERSION} ${ES_INSTALL_PATH}/elasticsearch
    cat > ${ES_INSTALL_PATH}/elasticsearch/bin/systemd-entrypoint<<EOF
#!/bin/sh
# This wrapper script allows SystemD to feed a file containing a passphrase into
# the main Elasticsearch startup script

if [ -n "\$ES_KEYSTORE_PASSPHRASE_FILE" ] ; then
  exec ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch "\$@" < "\$ES_KEYSTORE_PASSPHRASE_FILE"
else
  exec ${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch "\$@"
fi
EOF
    chmod +x ${ES_INSTALL_PATH}/elasticsearch/bin/systemd-entrypoint
}

config_es () {
    # create user and es data dir
    create_user ${ES_USER}
    create_dir ${ES_DATA_PATH} ${ES_LOGS_PATH}
    
    # change kernal args
    echo 'vm.max_map_count=262144' > /lib/sysctl.d/elasticsearch.conf
    sysctl -p /lib/sysctl.d/elasticsearch.conf

    # change default jvm
    sed -i -e "/-Xms/c -Xms${ES_JVM_XMS}" -e "/-Xmx/c -Xmx${ES_JVM_XMX}"\
    ${ES_INSTALL_PATH}/elasticsearch/config/jvm.options

    # init certs
    set_es_certs

    # set es config file elasticsearch.yml
    set_es_configfile

    # change files owner
    chown -R ${ES_USER}:${ES_USER}  ${ES_DATA_PATH} ${ES_LOGS_PATH}  ${ES_INSTALL_PATH}/elasticsearch-${ES_VERSION}   
    
    # init es systemd service and enable service
    init_es_service

    color "starting elasticsearch.service,please wait a few seconds..." 2

    # Start the program according to the version
    if [ "${ES_VERSION%%.*}" = "8" ];then
        es8.x_start_service
    elif [ "${ES_VERSION%%.*}" = "7" ];then
        es7.x_start_service
    else
        color "unknown version!" 1
        exit
    fi

    # print post install informations
    echo -e "***************************\E[1;32m readme \E[0;m*********************************"
    echo -e "elasticsearch security: \E[1;31m${ES_SECURITY}\E[0;m"
    echo "elasticsearch version: ${ES_VERSION}"
    echo
    echo "certdir: ${ES_CERTS_PATH}"
    echo "ca-cert: ${ES_CERTS_PATH}/${ES_SEFT_CA}"
    echo "http-cert: ${ES_CERTS_PATH}/${ES_HTTP_CERT}"
    echo "transport-cert: ${ES_CERTS_PATH}/${ES_TRANSPORT_CERT}"
    echo
    echo "config file path: ${ES_INSTALL_PATH}/elasticsearch/config"
    echo "data path: ${ES_DATA_PATH}"
    echo "log path: ${ES_LOGS_PATH}"
    echo "jvm config: Xms=${ES_JVM_XMS} Xmx=${ES_JVM_XMX}"    
    echo
    echo "superuser: elastic"
    echo -e "password:  \n${ES_INITIAL_PASSWORD}"
    echo
    echo "You can reset the password of the elastic built-in superuser with "
    echo
    
    if [ "${ES_VERSION%%.*}" = "7" ];then
    echo -e '\E[1;33mcurl -k -u elastic -H "Content-Type:application/json" -XPOST -u elastic \'
    echo -e "'http://127.0.0.1:9200/_xpack/security/user/elastic/_password' -d '{ \"password\" : \"NEWPASSWORD\" }'\E[0;m"
    echo -e "you can use \n\E[1;33mcurl -k -u elastic:NOWRPASSWORD ${HTTP_PREFIX}://127.0.0.1:9200\E[0;m\n to connect elasticsearch" 
    fi

    if [ "${ES_VERSION%%.*}" = "8" ];then
        echo -e "\E[1;33m${ES_INSTALL_PATH}/elasticsearch/bin/elasticsearch-reset-password -u elastic -i\E[0;m"
        echo
        echo -e "use \n\E[1;33mcurl -k -u elastic:NOWPASSWORD ${HTTP_PREFIX}://127.0.0.1:9200\E[0;m\n to connect elasticsearch"  
    fi
}

main () {
    if ! [[ ${ES_SECURITY} =~ ^true$|^false$ ]];then
        color "ES_SECURITY vars set is wrong,check script." 1
        exit
    fi
    judge_current_user
    process_file
    config_es
}
main